home *** CD-ROM | disk | FTP | other *** search
/ PC World Komputer 2010 April / PCWorld0410.iso / pluginy Firefox / 2257 / 2257.xpi / chrome / content / TextEditorLib.js < prev   
Text File  |  2006-03-21  |  18KB  |  513 lines

  1. /**
  2.  * TextEditorLib library class takes over typing functions
  3.  * of a browser that supports javascript1.5, it handles
  4.  * multiple TextNodes at the same time by storing text node references in a map.
  5.  * Thanks to Alex Benenson and Roman Mironenko
  6.  */
  7. var TextEditorLib = {
  8.  
  9.     // this map will contain all TextNodes.
  10.     textNodeMap : new Array(),
  11.  
  12.     isCapsOn : false,
  13.  
  14.     nodeId : 1,
  15.  
  16.     areAnyTextEditorsAttached : function() {
  17.         if (!TextEditorLib.isObjectEmpty(this.textNodeMap)) {
  18.             for(var i=0;i<TextEditorLib.textNodeMap.length;i++) {
  19.                 var txtNode = TextEditorLib.textNodeMap[i];
  20.                 if (!TextEditorLib.isObjectEmpty(txtNode)) {
  21.                     var attr = txtNode.txtnode.getAttribute("TextEditorLib.attribute.id");
  22.                     if (!TextEditorLib.isObjectEmpty(attr)) {
  23.                         return true;
  24.                     }
  25.                     
  26.                 }
  27.             }
  28.         }
  29.         return false;
  30.     },
  31.  
  32.     isObjectEmpty : function(obj) {
  33.         var r;
  34.         try {
  35.             r= obj==undefined || obj==null || obj.toString()=="";
  36.         } catch (e) {
  37.             r=true;
  38.         }
  39.         return r;
  40.     },
  41.  
  42.     isNodeEditor : function(node) {
  43.         return node.getEditor != undefined || node.contentDocument != undefined;
  44.     },
  45.  
  46.     toggleEditor : function(func) {
  47.         if (func==undefined || func==null) {
  48.             throw "Translation function undefined.";
  49.         }
  50.         var node = TextEditorLib.getNode();
  51.         if (node==null) {return null;}
  52.         var attr = node.getAttribute("TextEditorLib.attribute.id");
  53.         if (TextEditorLib.isObjectEmpty(attr)) {
  54.             attr=0;
  55.         }
  56.         var textNode = TextEditorLib.textNodeMap[attr];
  57.         if (textNode==undefined || TextEditorLib.isObjectEmpty(attr)) {
  58.             // define new text node
  59.             attr = this.nodeId;
  60.             node.setAttribute("TextEditorLib.attribute.id",attr);
  61.             this.nodeId++;
  62.             textNode = new TextNode.TextNode(node, "keypress", TextEditorLib.onKeyHandler, func, "keyup", TextEditorLib.handleCapsLockPressed, TextEditorLib.isNodeEditor(node));
  63.             textNode.setUp();
  64.             TextEditorLib.textNodeMap[attr]=textNode;
  65.         } else {
  66.             // destroy existing text node
  67.             textNode.tearDown();
  68.             delete TextEditorLib.textNodeMap[attr];
  69.             node.setAttribute("TextEditorLib.attribute.id",null);
  70.         }
  71.     },
  72.  
  73.     getTextNode : function() {
  74.         var node = TextEditorLib.getNode();
  75.         var attr = node.getAttribute("TextEditorLib.attribute.id");
  76.         if (TextEditorLib.isObjectEmpty(attr)) {
  77.             attr=0;
  78.         }
  79.         return TextEditorLib.textNodeMap[attr];
  80.     },
  81.  
  82.     // try to initialize this text node with a focused node from the document.
  83.     getNode : function() {
  84.         var node = document.commandDispatcher.focusedElement;
  85.         if (node==null) {
  86.             if (document.commandDispatcher.focusedWindow) {
  87.                 if (document.commandDispatcher.focusedWindow.document) { 
  88.                     // find ThunderBird compose window (oh, yes, this is riduculous.)
  89.                     var editors = document.getElementsByTagName("editor");
  90.                     for (var i = 0; i < editors.length; i++) {
  91.                         if (editors[i].contentWindow == document.commandDispatcher.focusedWindow) {
  92.                             return editors[i];
  93.                         }
  94.                     }
  95.                 }
  96.                 node = document.commandDispatcher.focusedWindow.frameElement;
  97.                 if (node && node.contentDocument && node.contentDocument.designMode=="on") {
  98.                     return node;  // midas in iframe.
  99.                 }
  100.             }
  101.         } else {
  102.             var name = node.localName.toUpperCase();
  103.             var type = node.type;
  104.             if (name=="TEXTAREA" || name=="TEXTBOX" || (name=="INPUT" && (type=="text" || type=="file"))) {
  105.                 if (!node.disabled && !node.readOnly) {
  106.                     return node;
  107.                 }
  108.             }
  109.         }
  110.         return null;
  111.     },
  112.  
  113.  
  114.     isEnterPressed : function(key) {
  115.         if (key==13) {
  116.             return true;
  117.         }
  118.         return false;
  119.     },
  120.  
  121.     handleCapsLockPressed : function(event) {
  122.         if (event.which==20) {
  123.             if (TextEditorLib.isCapsOn==false) {
  124.                 TextEditorLib.isCapsOn=true;
  125.             } else {
  126.                 TextEditorLib.isCapsOn=false;
  127.             }
  128.         }
  129.     },
  130.  
  131.     isCapitalLetter : function(isShiftOn) {
  132.         var isCapital=TextEditorLib.isCapsOn;
  133.         if (isShiftOn) {
  134.             if (isCapital) {
  135.                 isCapital=false;
  136.             } else {
  137.                 isCapital=true;
  138.             }
  139.         }
  140.         return isCapital;
  141.     },
  142.  
  143.     onKeyHandler : function(event) {
  144.         // if this event was already processed by this handler, do not process it again
  145.         if (event.keyCode == 255 && event.charCode > 0) {
  146.             // I can't explain why this needs to be done, but midas will fail
  147.             // to process event otherwise.
  148.             if (event.target.nodeName == "HTML")
  149.                 for (var i in event) if (("" + i).toUpperCase()) event[i];
  150.             return;
  151.         }
  152.         if (event.charCode > 0 && !event.ctrlKey && !event.altKey && !event.metaKey) {
  153.             var txtnode = TextEditorLib.getTextNode();
  154.             // is shift on, is typed letter capital?
  155.             var isShiftOn = event.shiftKey;
  156.             var isCapital=TextEditorLib.isCapitalLetter(isShiftOn);
  157.             // translate typed character
  158.             var translator = txtnode.translationFunction;
  159.             var ch = translator(event.which, isCapital, isShiftOn);
  160.             if (ch==-1) {
  161.                 return;
  162.             }
  163.             event.preventDefault();
  164.             // in case if the return value is an array, print each element of the array in sequence.
  165.             if (ch instanceof Array) {
  166.                 for(var i=0;i<ch.length;i++) {
  167.                     var newEvent = document.createEvent("KeyEvents");
  168.                     newEvent.initKeyEvent(event.type, event.canBubble, event.cancelable, event.view, false, false, false, false, 255, ch[i]);
  169.                     event.target.dispatchEvent(newEvent);
  170.                 }
  171.             } else {
  172.                 var newEvent = document.createEvent("KeyEvents");
  173.                 newEvent.initKeyEvent(event.type, event.canBubble, event.cancelable, event.view, false, false, false, false, 255, ch);
  174.                 event.target.dispatchEvent(newEvent);
  175.             }
  176.         }
  177.     },
  178.  
  179.     // transformer functions
  180.  
  181.     getSelection : function(node) {
  182.         if (node==null) {
  183.             // retrieve global selection
  184.             var window = document.commandDispatcher.focusedWindow;
  185.             if (window && window.getSelection) {
  186.                 return window.getSelection();
  187.             }
  188.         } else {
  189.             // return selection from editor or from midas content window
  190.             return node.getSelection ? node.getSelection() : node.contentWindow.getSelection();
  191.         }
  192.         return null;
  193.     },
  194.  
  195.     splitHtmlString: function(string) {
  196.         var re = /<[\/]?[!A-Z][^>]*>/ig;
  197.         var result = new Array();
  198.         var lastIndex = 0;
  199.         var arr = null;
  200.         while ( (arr = re.exec(string)) != null) {
  201.             result[result.length] = string.substring(lastIndex, arr.index);
  202.             result[result.length] = string.substring(arr.index, re.lastIndex);
  203.             lastIndex = re.lastIndex;
  204.         }
  205.         result[result.length] = string.substr(lastIndex);
  206.         return result;
  207.     },
  208.  
  209.     convertWithHTML: function(src, skipHtml, converter) {
  210.         if (src == "" || src == null) return src;
  211.         if (!skipHtml) {
  212.             return converter(src);
  213.         } else {
  214.             var arr = TextEditorLib.splitHtmlString(src);
  215.             for (var i = 0; i < arr.length; i++) {
  216.                 if ( (i % 2) == 0) arr[i] = converter(arr[i]);
  217.             }
  218.             return arr.join("");
  219.         }
  220.     },
  221.     
  222.     insertNodeAtSelection : function(selection, insertNode)
  223.     {
  224.         var range = selection.getRangeAt(0);
  225.         selection.removeAllRanges();
  226.         range.deleteContents();
  227.         var container = range.startContainer;
  228.         var pos = range.startOffset;
  229.         if (!pos) {
  230.             var newNode = document.createTextNode(" ");
  231.             range.insertNode(newNode);
  232.             selection.addRange(range);
  233.             range = selection.getRangeAt(0);
  234.             selection.removeAllRanges();
  235.             range.deleteContents();
  236.             container = range.startContainer;
  237.             pos = range.startOffset;
  238.         }
  239.         range=document.createRange();
  240.         // insertion logic
  241.         // special case inserting text into text node
  242.         if (container.nodeType==insertNode.TEXT_NODE && insertNode.nodeType==insertNode.TEXT_NODE) {
  243.             container.insertData(pos, insertNode.nodeValue);
  244.             // set cursor position at the end
  245.             range.setEnd(container, pos+insertNode.length);
  246.             range.setStart(container, pos+insertNode.length);
  247.         } else {
  248.             var afterNode;
  249.             if (container.nodeType==insertNode.TEXT_NODE) {
  250.                 // inserting into a textnode, create 2 new nodes
  251.                 // insert the new node inbetween
  252.                 var textNode = container;
  253.                 container = textNode.parentNode;
  254.                 var text = textNode.nodeValue;
  255.                 // text before the split
  256.                 var textBefore = text.substr(0,pos);
  257.                 // text after the split
  258.                 var textAfter = text.substr(pos);
  259.                 var beforeNode = document.createTextNode(textBefore);
  260.                 afterNode = document.createTextNode(textAfter);
  261.                 // insert the all new nodes before the old one
  262.                 container.insertBefore(afterNode, textNode);
  263.                 container.insertBefore(insertNode, afterNode);
  264.                 container.insertBefore(beforeNode, insertNode);
  265.                 // remove the old node
  266.                 container.removeChild(textNode);
  267.             } else {
  268.                 // else simply insert the node
  269.                 afterNode = container.childNodes[pos];
  270.                 container.insertBefore(insertNode, afterNode);
  271.             }
  272.             // set cursor
  273.             range.setEnd(afterNode, 0);
  274.             range.setStart(afterNode, 0);
  275.         }
  276.         selection.addRange(range);
  277.     },
  278.  
  279.     isTransformableNodeType : function(node) {
  280.         return node.nodeType == node.TEXT_NODE || node.nodeType == node.PROCESSING_INSTRUCTION_NODE || node.nodeType == node.COMMENT_NODE;
  281.     },
  282.  
  283.     isBeginningOfTransformableContainer : function(range, node, state) {
  284.         var startContainer = range.startContainer;
  285.         if (!state.started && 
  286.             ((TextEditorLib.isTransformableNodeType(startContainer) && node == state.range.startContainer) ||
  287.                     (startContainer.childNodes && node == startContainer.childNodes[range.startOffset]))) {
  288.             return true;
  289.         }
  290.         return false;
  291.     },
  292.  
  293.     isEndOfTransformableContainer : function(range, node, state) {
  294.         var endContainer = range.endContainer;
  295.         if (!state.finished &&
  296.             ((TextEditorLib.isTransformableNodeType(endContainer) && node == endContainer) || ((endContainer.childNodes.length > 0) &&
  297.                     node == endContainer.childNodes[state.range.endOffset - 1]))) {
  298.             return true;
  299.         }
  300.         return false;
  301.     },
  302.  
  303.     NodeRangeConversionState : function(state, node) {
  304.         this.node = node;
  305.         this.range = state.range;
  306.         this.convert = state.convert;
  307.         this.isAsynchronous = state.isAsynchronous;
  308.         this.started = state.started;
  309.         this.finished = state.finished;
  310.         this.converted = state.converted;
  311.         this.toString = function() {
  312.             return "started : " + this.started + ", finished: " + this.finished;
  313.         };
  314.     },
  315.  
  316.     transformNodeText : function(node, state) {
  317.         var start = (node == state.range.startContainer) ? state.range.startOffset : 0;
  318.         var end   = (node == state.range.endContainer) ? state.range.endOffset : node.nodeValue.length;
  319.         var remainder = (node == state.range.endContainer) ? node.nodeValue.length - state.range.endOffset : 0;
  320.         var convertedValue;
  321.         if (state.isAsynchronous) {
  322.             var stateCopy = new TextEditorLib.NodeRangeConversionState(state, node);
  323.             // call asynchronous conversion function, pass it the text to be converted and
  324.             // copy of the state, so that the conversion function can initiate conversion, return
  325.             // and at the end of conversion make a call back to this function with the copied state
  326.             // in synchronous mode.
  327.             var conversionResult = state.convert(node.nodeValue.substring(start, end), stateCopy);
  328.             // if conversionResult is not null, then the call was resolved synchronously (from cache,)
  329.             // and there will be no asynchronous call back.
  330.             if (conversionResult!=null) {
  331.                 convertedValue = node.nodeValue.substring(0, start) + conversionResult + node.nodeValue.substr(end);
  332.             }
  333.         } else {
  334.             // call synchronous conversion function.
  335.             convertedValue = node.nodeValue.substring(0, start) + state.convert(node.nodeValue.substring(start, end)) + node.nodeValue.substr(end);
  336.         }
  337.         state.converted = true;
  338.         if (!TextEditorLib.isObjectEmpty(convertedValue)) {
  339.             node.nodeValue = convertedValue;
  340.             if (node == state.range.endContainer) {
  341.                 state.range.setEnd(node, node.nodeValue.length - remainder);
  342.             }
  343.             if (node == state.range.startContainer) {
  344.                 state.range.setStart(node, start);
  345.             }
  346.         }
  347.     },
  348.  
  349.     RangeConversionState: function(range, converter, isAsynchronous) {
  350.         this.range = range;
  351.         this.convert = converter;
  352.         this.isAsynchronous = isAsynchronous;
  353.         this.started = false;
  354.         this.finished = false;
  355.         this.converted = false;
  356.         this.toString = function() {
  357.             return "started : " + this.started + ", finished: " + this.finished;
  358.         };
  359.     },
  360.  
  361.     transformRangeNode : function(node, state) {
  362.         if (state.started && state.finished) {
  363.             return;
  364.         }
  365.         if (TextEditorLib.isBeginningOfTransformableContainer(state.range, node, state)) {
  366.             state.started = true;
  367.         }
  368.         if (TextEditorLib.isTransformableNodeType(node)) {
  369.             if (state.started && !state.finished) {
  370.                 TextEditorLib.transformNodeText(node, state);
  371.             }
  372.         } else if (node.childNodes) {
  373.             for (var i = 0; i < node.childNodes.length; i++) {
  374.                 TextEditorLib.transformRangeNode(node.childNodes[i], state);
  375.                 if (state.started && state.finished)
  376.                 break;
  377.             }
  378.         }
  379.         if (TextEditorLib.isEndOfTransformableContainer(state.range, node, state)) {
  380.             state.finished = true;
  381.         }
  382.     },
  383.  
  384.     transformHTMLSelection : function(selection, transformerFunc, isAsynchronous) {
  385.         if (selection==null || TextEditorLib.isObjectEmpty(transformerFunc)) {
  386.             return;
  387.         }
  388.         for (var i=0;i<selection.rangeCount;i++) {
  389.             var conversionState = new TextEditorLib.RangeConversionState(selection.getRangeAt(i), transformerFunc, isAsynchronous);
  390.             TextEditorLib.transformRangeNode(selection.getRangeAt(i).commonAncestorContainer, conversionState);
  391.             if (!conversionState.converted && selection.getRangeAt(i).toString().length>0) {
  392.                 // extra conversion handling
  393.                 var range = selection.getRangeAt(i);
  394.                 var newNode = document.createTextNode(transformerFunc(range.toString()));
  395.                 TextEditorLib.insertNodeAtSelection(selection, newNode);
  396.             }
  397.         }
  398.         
  399.     },
  400.  
  401.     transformSelectionText : function(transformerFunc, skipHtml, isAsynchronous) {
  402.         var node = TextEditorLib.getNode();
  403.         if (node!=null || !TextEditorLib.isObjectEmpty(TextEditorLib.getSelection(null))) {
  404.             if (node==null || TextEditorLib.isNodeEditor(node)) {
  405.                 // transform text in an HTML document or HTML editor
  406.                 TextEditorLib.transformHTMLSelection(TextEditorLib.getSelection(node), transformerFunc, isAsynchronous);                
  407.             } else if (node!=null) {
  408.                 // transform text in a text input field or textarea
  409.                 var oldValue = node.value;
  410.                 var selStart = node.selectionStart;
  411.                 var selEnd = node.selectionEnd;
  412.                 var value = oldValue.substring(selStart, selEnd);
  413.                 var newValue = TextEditorLib.convertWithHTML(value, skipHtml, transformerFunc);
  414.                 var scrollTop = node.scrollTop;
  415.                 node.value = oldValue.substring(0, selStart) + newValue + oldValue.substring(selEnd, oldValue.length);
  416.                 node.selectionStart = selStart + value.length;
  417.                 node.selectionEnd = selStart + value.length;
  418.             }
  419.         }
  420.     }
  421.     
  422. }
  423.  
  424. /**
  425.  * TextNode class is used to hold reference to a text editor field.
  426.  * TextNode can initialize itself from a document node, it handles
  427.  * event listeners on the editor field.
  428.  */
  429.  
  430. var TextNode = {
  431.     //txtnode : undefined,
  432.     //eventToListen : undefined,
  433.     //translationFunction : undefined,
  434.  
  435.     TextNode : function(node, onKeyEventToListen, onKeyHandlerFunc, translationFunc, onCapsLockEventToListen, onCapsLockHandlerFunc, isNodeEditor) {
  436.         this.txtnode = node;
  437.         this.eventToListen = onKeyEventToListen;
  438.         this.onKeyHandlerFunc = onKeyHandlerFunc;
  439.         this.translationFunction = translationFunc;
  440.         this.onCapsLockEventToListen = onCapsLockEventToListen;
  441.         this.onCapsLockHandlerFunc = onCapsLockHandlerFunc;
  442.         this.isNodeEditor = isNodeEditor;
  443.         this.borderStyle = null;
  444.         this.backgroundColor = null;
  445.         // attach object's methods
  446.         this.getNode = TextNode.getNode;
  447.         this.setUp = TextNode.setUp;
  448.         this.tearDown = TextNode.tearDown;
  449.         this.attachListener = TextNode.attachListener;
  450.         this.detachListener = TextNode.detachListener;
  451.     },
  452.  
  453.     getTranslationFunction : function() {
  454.         return this.translationFunction;
  455.     },
  456.  
  457.     getNode : function() {
  458.         return this.txtnode;
  459.     },
  460.  
  461.     setUp : function() {
  462.         this.attachListener(this.eventToListen, this.onKeyHandlerFunc);
  463.         this.attachListener(this.onCapsLockEventToListen, this.onCapsLockHandlerFunc);
  464.         // set some stupid style parameters.
  465.         var style = "dashed 1px red";
  466.         if (this.isNodeEditor) {
  467.             this.borderStyle = this.txtnode.style.border;
  468.             this.txtnode.style.border = style;
  469.         } else {
  470.             this.borderStyle = this.txtnode.style.outline;
  471.             this.txtnode.style.outline = style;
  472.         }
  473.         this.backgroundColor = document.defaultView.getComputedStyle(this.txtnode, "").getPropertyValue("background-color");
  474.         var newRGB = getModifiedColor(this.backgroundColor, -15,0,-15);
  475.         this.txtnode.style.backgroundColor=newRGB;
  476.     },
  477.  
  478.     tearDown : function() {
  479.         this.detachListener(this.eventToListen, this.onKeyHandlerFunc);
  480.         this.detachListener(this.onCapsLockEventToListen, this.onCapsLockHandlerFunc);
  481.         // unset some stupid style parameters.
  482.         this.txtnode.style.backgroundColor=this.backgroundColor;
  483.         if (this.isNodeEditor) {
  484.             this.txtnode.style.border = this.borderStyle;
  485.         } else {
  486.             this.txtnode.style.outline = this.borderStyle;
  487.         }
  488.     },
  489.  
  490.     // attaches a listener to the txtnode, registers func to process, false - for bubbling
  491.     attachListener : function(eventToListen, onKeyHandlerFunc) {
  492.         if (eventToListen == undefined || onKeyHandlerFunc == undefined) {
  493.             throw "Cannot attach listener.";
  494.         }
  495.         if (this.txtnode.contentDocument) {
  496.             this.txtnode.contentDocument.documentElement.addEventListener(eventToListen, onKeyHandlerFunc, false);
  497.         } else {
  498.             this.txtnode.addEventListener(eventToListen, onKeyHandlerFunc, false);
  499.         }
  500.     },
  501.  
  502.     // detaches previously attached listener from the txtnode
  503.     detachListener : function(eventToListen, onKeyHandlerFunc) {
  504.         if (eventToListen == undefined || onKeyHandlerFunc == undefined) {
  505.             throw "Cannot detach listener.";
  506.         }
  507.         if (this.txtnode.contentDocument) {
  508.             this.txtnode.contentDocument.documentElement.removeEventListener(eventToListen, onKeyHandlerFunc, false);
  509.         } else {
  510.             this.txtnode.removeEventListener(eventToListen, onKeyHandlerFunc, false);
  511.         }
  512.     }
  513. }